package com.free.mini.gateway.service.body;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import com.free.mini.common.utils.BizTraceUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import com.free.mini.gateway.constants.GatewayConstant;
import com.free.mini.gateway.filter.GatewayContext;
import com.free.mini.gateway.service.ParseRequestBody;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;

/**
 * @author dongsufeng
 * @version 4.0
 * @date 2023/8/7 18:56
 */
@Service
@Slf4j
public class FormDataContentType implements ParseRequestBody {

    @PostConstruct
    public void init(){
        ParseRequestBody.map.put(MediaType.APPLICATION_FORM_URLENCODED.getType(),this);
    }
    @Override
    public Boolean support(GatewayContext gatewayContext) {
        MediaType contentType = gatewayContext.getContentType();
        if (MediaType.APPLICATION_FORM_URLENCODED.getType().equals(contentType.getType())
            && MediaType.APPLICATION_FORM_URLENCODED.getSubtype().equals(contentType.getSubtype())) {
            if (log.isDebugEnabled()) {
                log.debug("do RequestFilter [Content-Type: application/x-www-form-urlencoded].");
            }
            return true;
        }
        return false;
    }

    @Override
    public Mono<Void> parseBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
        final ServerHttpRequest request = gatewayContext.getServerHttpRequest();
        HttpHeaders headers = request.getHeaders();
        return exchange.getFormData().doOnNext(multiValueMap -> {
            gatewayContext.setFormData(multiValueMap);
        }).then(Mono.defer(() -> {
            Charset charset = headers.getContentType().getCharset();
            charset = charset == null ? StandardCharsets.UTF_8 : charset;
            String charsetName = charset.name();
            MultiValueMap<String, String> formData = gatewayContext.getFormData();
            if (CollectionUtils.isEmpty(formData)) {
                if (log.isDebugEnabled()) {
                    log.info("multiValueMap is empty");
                }
                return chain.filter(exchange);
            }
            /**
             * 重构请求参数
             */
            StringBuilder formDataBodyBuilder = new StringBuilder();
            try {
                for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
                    String entryKey = entry.getKey();
                    List<String> entryValue = entry.getValue();
                    for (String value : entryValue) {
                        rebuildFormDataByKey(gatewayContext, charsetName, formDataBodyBuilder, entryKey, value);
                    }
                }
            } catch (Exception e) {
                log.error("handleFormData参数解析异常", e);
            }
            /**
             * 正常返回
             */
            // substring with the last char '&'
            String formDataBodyString = formDataBodyBuilder.length() == 0 ? ""
                    : formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1);
            if (log.isDebugEnabled()) {
                log.debug("handleFormData参数解析结果:{}", formDataBodyString);
            }
            byte[] bodyBytes = formDataBodyString.getBytes(charset);
            int contentLength = bodyBytes.length;
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {
                @Override
                public HttpHeaders getHeaders() {
                    gatewayContext.setHeaders(HttpHeaders.TRANSFER_ENCODING, GatewayConstant.CHUNKED);
                    gatewayContext.removeContentLength();
                    if (contentLength > 0) {
                        gatewayContext.setContentLength(contentLength);
                    }
                    HttpHeaders httpHeaders = gatewayContext.getHeaders();
                    headers.add(BizTraceUtil.BIZ_TRACE_ID,gatewayContext.getBizTraceId());
                    return httpHeaders;
                }
                @Override
                public Flux<DataBuffer> getBody() {
                    return DataBufferUtils.read(new ByteArrayResource(bodyBytes),
                            exchange.getResponse().bufferFactory(), contentLength);
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build());
        }));
    }

    @Override
    public void decryptAndSign(String reqStr, GatewayContext gatewayContext) {

    }
    private void rebuildFormDataByKey(GatewayContext gatewayContext, String charsetName, StringBuilder formDataBodyBuilder,
                                      String entryKey, String value) throws UnsupportedEncodingException {
        if (GatewayConstant.DATA.equals(entryKey)) {
            decryptAndSign(value, gatewayContext);
            formDataBodyBuilder.append(GatewayConstant.DATA)
                        .append(GatewayConstant.EQUALS)
                        .append(URLEncoder.encode(value, charsetName))
                        .append(GatewayConstant.AND);
        } else {
            formDataBodyBuilder.append(entryKey)
                    .append(GatewayConstant.EQUALS)
                    .append(URLEncoder.encode(value, charsetName))
                    .append(GatewayConstant.AND);
        }
    }
}
